/*-------------------------------------------------------------------------
 *
 * socket.c
 *	  Microsoft Windows Win32 套接字函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/port/win32/socket.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

/*
 * 指示 pgwin32_recv() 和 pgwin32_send() 是否应该在非阻塞模式下操作。
 *
 * 由于套接字仿真层始终将实际套接字设置为非阻塞模式以能够传递信号，
 * 如果我们确实需要非阻塞操作，就必须在一个单独的标志中指定这一点。
 *
 * 此标志会*全局*改变所有套接字操作的行为，
 * 因此它只应在非常短的时间内设置。
 */
int			pgwin32_noblock = 0;

/* 取消在 win32.h 中定义的宏，以便我们可以访问系统函数 */
#undef socket
#undef bind
#undef listen
#undef accept
#undef connect
#undef select
#undef recv
#undef send

/*
 * 实现的阻塞套接字函数，使其同时在套接字和信号事件上监听，
 * 这是信号处理所必需的。
 */

/*
 * 将最后的套接字错误代码转换为 errno
 *
 * 注意：当 WSAxxx 错误代码与 Berkeley 错误符号之间存在直接对应关系时，
 * 这个映射实际上是一个无操作，因为在 win32_port.h 中，我们重新定义
 * 与网络相关的 Berkeley 错误符号为它们的 WSAxxx 对应值。此切换的目的主要是
 * 将几乎接近错误代码转换为在 Berkeley 世界中合理的东西。
 */
static void fc_TranslateSocketError(void)
{
	switch (WSAGetLastError())
	{
		case WSAEINVAL:
		case WSANOTINITIALISED:
		case WSAEINVALIDPROVIDER:
		case WSAEINVALIDPROCTABLE:
		case WSAEDESTADDRREQ:
			errno = EINVAL;
			break;
		case WSAEINPROGRESS:
			errno = EINPROGRESS;
			break;
		case WSAEFAULT:
			errno = EFAULT;
			break;
		case WSAEISCONN:
			errno = EISCONN;
			break;
		case WSAEMSGSIZE:
			errno = EMSGSIZE;
			break;
		case WSAEAFNOSUPPORT:
			errno = EAFNOSUPPORT;
			break;
		case WSAEMFILE:
			errno = EMFILE;
			break;
		case WSAENOBUFS:
			errno = ENOBUFS;
			break;
		case WSAEPROTONOSUPPORT:
		case WSAEPROTOTYPE:
		case WSAESOCKTNOSUPPORT:
			errno = EPROTONOSUPPORT;
			break;
		case WSAECONNABORTED:
			errno = ECONNABORTED;
			break;
		case WSAECONNREFUSED:
			errno = ECONNREFUSED;
			break;
		case WSAECONNRESET:
			errno = ECONNRESET;
			break;
		case WSAEINTR:
			errno = EINTR;
			break;
		case WSAENOTSOCK:
			errno = ENOTSOCK;
			break;
		case WSAEOPNOTSUPP:
			errno = EOPNOTSUPP;
			break;
		case WSAEWOULDBLOCK:
			errno = EWOULDBLOCK;
			break;
		case WSAEACCES:
			errno = EACCES;
			break;
		case WSAEADDRINUSE:
			errno = EADDRINUSE;
			break;
		case WSAEADDRNOTAVAIL:
			errno = EADDRNOTAVAIL;
			break;
		case WSAEHOSTDOWN:
			errno = EHOSTDOWN;
			break;
		case WSAEHOSTUNREACH:
		case WSAHOST_NOT_FOUND:
			errno = EHOSTUNREACH;
			break;
		case WSAENETDOWN:
			errno = ENETDOWN;
			break;
		case WSAENETUNREACH:
			errno = ENETUNREACH;
			break;
		case WSAENETRESET:
			errno = ENETRESET;
			break;
		case WSAENOTCONN:
		case WSAESHUTDOWN:
		case WSAEDISCON:
			errno = ENOTCONN;
			break;
		case WSAETIMEDOUT:
			errno = ETIMEDOUT;
			break;
		default:
			ereport(NOTICE,
					(errmsg_internal("unrecognized win32 socket error code: %d",
									 WSAGetLastError())));
			errno = EINVAL;
			break;
	}
}

static int fc_pgwin32_poll_signals(void)
{
	if (UNBLOCKED_SIGNAL_QUEUE())
	{
		pgwin32_dispatch_queued_signals();
		errno = EINTR;
		return 1;
	}
	return 0;
}

static int fc_isDataGram(SOCKET fc_s)
{
	int			fc_type;
	int			fc_typelen = sizeof(fc_type);

	if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &fc_type, &fc_typelen))
		return 1;

	return (fc_type == SOCK_DGRAM) ? 1 : 0;
}

int pgwin32_waitforsinglesocket(SOCKET fc_s, int fc_what, int fc_timeout)
{
	static HANDLE fc_waitevent = INVALID_HANDLE_VALUE;
	static SOCKET fc_current_socket = INVALID_SOCKET;
	static int	fc_isUDP = 0;
	HANDLE		events[2];
	int			fc_r;

	/* 只创建一个事件对象，并在所有后续调用中使用它 */
	if (fc_waitevent == INVALID_HANDLE_VALUE)
	{
		fc_waitevent = CreateEvent(NULL, TRUE, FALSE, NULL);

		if (fc_waitevent == INVALID_HANDLE_VALUE)
			ereport(ERROR,
					(errmsg_internal("could not create socket waiting event: error code %lu", GetLastError())));
	}
	else if (!ResetEvent(fc_waitevent))
		ereport(ERROR,
				(errmsg_internal("could not reset socket waiting event: error code %lu", GetLastError())));

	/*
	 * 跟踪套接字是否为 UDP。 (注意：这很可能是既无用又错误的；
	 * 没有理由认为 WSAEventSelect 对 TCP 和 UDP 的行为不同。)
	 */
	if (fc_current_socket != fc_s)
		fc_isUDP = isDataGram(fc_s);
	fc_current_socket = fc_s;

	/*
	 * 将事件附加到套接字。 注意：在返回之前我们必须再次分离它，
	 * 因为其他代码可能会尝试将其他事件附加到套接字。
	 */
	if (WSAEventSelect(fc_s, fc_waitevent, fc_what) != 0)
	{
		fc_TranslateSocketError();
		return 0;
	}

	events[0] = pgwin32_signal_event;
	events[1] = fc_waitevent;

	/*
	 * 只是一个未知锁定问题的变通办法，在高负载下向 UDP 套接字写入：
	 * 客户端的 pgsql 后台在 WaitForMultipleObjectsEx 中无限睡眠，
	 * pgstat 进程在 pgwin32_select() 中睡眠。
	 * 因此，我们将以小的超时（0.1 秒）等待，如果套接字仍然被阻塞，
	 * 尝试 WSASend（见 pgwin32_select 中的注释）并再次等待。
	 */
	if ((fc_what & FD_WRITE) && fc_isUDP)
	{
		for (;;)
		{
			fc_r = WaitForMultipleObjectsEx(2, events, FALSE, 100, TRUE);

			if (fc_r == WAIT_TIMEOUT)
			{
				char		fc_c;
				WSABUF		buf;
				DWORD		sent;

				buf.buf = &c;
				buf.len = 0;

				fc_r = WSASend(fc_s, &buf, 1, &sent, 0, NULL, NULL);
				if (fc_r == 0)		/* 完成 - 表示一切正常！ */
				{
					WSAEventSelect(fc_s, NULL, 0);
					return 1;
				}
				else if (WSAGetLastError() != WSAEWOULDBLOCK)
				{
					fc_TranslateSocketError();
					WSAEventSelect(fc_s, NULL, 0);
					return 0;
				}
			}
			else
				break;
		}
	}
	else
		fc_r = WaitForMultipleObjectsEx(2, events, FALSE, fc_timeout, TRUE);

	WSAEventSelect(s, NULL, 0);

	if (fc_r == WAIT_OBJECT_0 || fc_r == WAIT_IO_COMPLETION)
	{
		pgwin32_dispatch_queued_signals();
		errno = EINTR;
		return 0;
	}
	if (fc_r == WAIT_OBJECT_0 + 1)
		return 1;
	if (fc_r == WAIT_TIMEOUT)
	{
		errno = EWOULDBLOCK;
		return 0;
	}
	ereport(ERROR,
			(errmsg_internal("unrecognized return value from WaitForMultipleObjects: %d (error code %lu)", fc_r, GetLastError())));
	return 0;
}

/*
 * 创建一个套接字，将其设置为重叠和非阻塞
 */
SOCKET
pgwin32_socket(int fc_af, int fc_type, int fc_protocol)
{
	SOCKET		s;
	unsigned long fc_on = 1;

	s = WSASocket(fc_af, fc_type, fc_protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (s == INVALID_SOCKET)
	{
		fc_TranslateSocketError();
		return INVALID_SOCKET;
	}

	if (ioctlsocket(s, FIONBIO, &on))
	{
		fc_TranslateSocketError();
		closesocket(s);
		return INVALID_SOCKET;
	}
	errno = 0;

	return s;
}

int pgwin32_bind(SOCKET fc_s, struct sockaddr *fc_addr, int fc_addrlen)
{
	int			fc_res;

	fc_res = bind(fc_s, fc_addr, fc_addrlen);
	if (fc_res < 0)
		fc_TranslateSocketError();
	return fc_res;
}

int pgwin32_listen(SOCKET fc_s, int fc_backlog)
{
	int			fc_res;

	fc_res = listen(fc_s, fc_backlog);
	if (fc_res < 0)
		fc_TranslateSocketError();
	return fc_res;
}

SOCKET
pgwin32_accept(SOCKET fc_s, struct sockaddr *fc_addr, int *fc_addrlen)
{
	SOCKET		rs;

	/*
	 * 轮询信号，但不要以 EINTR 返回，因为我们不在 pqcomm.c 中处理它
	 */
	fc_pgwin32_poll_signals();

	rs = WSAAccept(fc_s, fc_addr, fc_addrlen, NULL, 0);
	if (rs == INVALID_SOCKET)
	{
		fc_TranslateSocketError();
		return INVALID_SOCKET;
	}
	return rs;
}


/* 连接期间没有信号传递。 */
int pgwin32_connect(SOCKET fc_s, const struct sockaddr *fc_addr, int fc_addrlen)
{
	int			fc_r;

	fc_r = WSAConnect(fc_s, fc_addr, fc_addrlen, NULL, NULL, NULL, NULL);
	if (fc_r == 0)
		return 0;

	if (WSAGetLastError() != WSAEWOULDBLOCK)
	{
		fc_TranslateSocketError();
		return -1;
	}

	while (pgwin32_waitforsinglesocket(fc_s, FD_CONNECT, INFINITE) == 0)
	{
		/* 只要我们在转发信号，就无限循环 */
	}

	return 0;
}

int pgwin32_recv(SOCKET fc_s, char *fc_buf, int fc_len, int fc_f)
{
	WSABUF		wbuf;
	int			fc_r;
	DWORD		b;
	DWORD		flags = f;
	int			fc_n;

	if (fc_pgwin32_poll_signals())
		return -1;

	wbuf.len = len;
	wbuf.buf = buf;

	fc_r = WSARecv(s, &wbuf, 1, &b, &flags, NULL, NULL);
	if (fc_r != SOCKET_ERROR)
		return b;				/* 成功 */

	if (WSAGetLastError() != WSAEWOULDBLOCK)
	{
		fc_TranslateSocketError();
		return -1;
	}

	if (pgwin32_noblock)
	{
		/*
		 * 没有收到数据，我们处于“模拟非阻塞模式”，因此
		 * 返回指示如果我们继续将会阻塞。
		 */
		errno = EWOULDBLOCK;
		return -1;
	}

	/* 我们处于阻塞模式，因此等待数据 */

	for (fc_n = 0; fc_n < 5; fc_n++)
	{
		if (pgwin32_waitforsinglesocket(fc_s, FD_READ | FD_CLOSE | FD_ACCEPT,
										INFINITE) == 0)
			return -1;			/* errno 已经设置 */

		fc_r = WSARecv(s, &wbuf, 1, &b, &flags, NULL, NULL);
		if (fc_r != SOCKET_ERROR)
			return b;			/* 成功 */
		if (WSAGetLastError() != WSAEWOULDBLOCK)
		{
			fc_TranslateSocketError();
			return -1;
		}

		/*
		 * 在 win2k（至少）上似乎存在一些情况，WSARecv 可以返回
		 * WSAEWOULDBLOCK，即使 pgwin32_waitforsinglesocket 声称套接字是可读的。
		 * 在这种情况下，只需睡眠片刻并重试。
		 * 我们最多重试 5 次 - 如果失败超过这个次数，它可能永远不会恢复。
		 */
		pg_usleep(10000);
	}
	ereport(NOTICE,
			(errmsg_internal("could not read from ready socket (after retries)")));
	errno = EWOULDBLOCK;
	return -1;
}

/*
 * send()的第二个参数被SUS定义为"const void *"
 * 所以我们在这里使用相同的签名，以使编译器在处理调用者时更加友好。
 *
 * 但是WSABUF结构的buf成员被定义为"char *"，所以我们在这里将第二个参数转型为该类型，
 * 同样是为了让编译器更加友好。
 */

int pgwin32_send(SOCKET fc_s, const void *fc_buf, int fc_len, int fc_flags)
{
	WSABUF		wbuf;
	int			fc_r;
	DWORD		b;

	if (fc_pgwin32_poll_signals())
		return -1;

	wbuf.len = fc_len;
	wbuf.buf = (char *) fc_buf;

	/*
	 * 将数据发送到UDP套接字的套接字的就绪状态可能不真实：套接字
	 * 可能再次变得繁忙！所以循环直到发送或发生错误。
	 */
	for (;;)
	{
		fc_r = WSASend(fc_s, &wbuf, 1, &b, fc_flags, NULL, NULL);
		if (fc_r != SOCKET_ERROR && b > 0)
			/* 写入立即成功 */
			return b;

		if (fc_r == SOCKET_ERROR &&
			WSAGetLastError() != WSAEWOULDBLOCK)
		{
			fc_TranslateSocketError();
			return -1;
		}

		if (pgwin32_noblock)
		{
			/*
			 * 没有发送数据，并且我们处于"模拟非阻塞模式"，所以
			 * 返回表示如果继续的话我们会阻塞。
			 */
			errno = EWOULDBLOCK;
			return -1;
		}

		/* 没有错误，零字节（win2000+）或错误+WSAEWOULDBLOCK（<=nt4） */

		if (pgwin32_waitforsinglesocket(fc_s, FD_WRITE | FD_CLOSE, INFINITE) == 0)
			return -1;
	}

	return -1;
}


/*
 * 等待一个或多个套接字的活动。
 * 在等待时，允许信号运行。
 *
 * 注意！当前不实现exceptfds检查，
 * 因为它在postgresql中未使用！
 */
int pgwin32_select(int fc_nfds, fd_set *fc_readfds, fd_set *fc_writefds, fd_set *fc_exceptfds, const struct timeval *fc_timeout)
{
	WSAEVENT	events[FD_SETSIZE * 2]; /* 最坏的情况是readfds与writefds完全
										 * 不同，因此
										 * 2*FD_SETSIZE个套接字 */
	SOCKET		sockets[FD_SETSIZE * 2];
	int			fc_numevents = 0;
	int			fc_i;
	int			fc_r;
	DWORD		timeoutval = WSA_INFINITE;
	FD_SET		outreadfds;
	FD_SET		outwritefds;
	int			fc_nummatches = 0;

	Assert(fc_exceptfds == NULL);

	if (fc_pgwin32_poll_signals())
		return -1;

	FD_ZERO(&outreadfds);
	FD_ZERO(&outwritefds);

	/*
	 * Windows并不保证记录一个FD_WRITE网络事件，指示
	 * 可以发送更多数据，除非之前的send()由于
	 * WSAEWOULDBLOCK而失败。虽然我们的调用者可能确实进行了这样的调用，但
	 * 我们不能在这里假设。因此，如果等待写就绪，请强制
	 * 问题，通过做一个虚拟的send()。如果虚拟的send()成功，
	 * 假设套接字实际上是可写的，并立即返回。
	 * 此外，如果它失败并且不是WSAEWOULDBLOCK，则返回一个
	 * 写就绪指示，让我们的调用者处理错误情况。
	 */
	if (fc_writefds != NULL)
	{
		for (fc_i = 0; fc_i < fc_writefds->fd_count; fc_i++)
		{
			char		fc_c;
			WSABUF		buf;
			DWORD		sent;

			buf.buf = &fc_c;
			buf.len = 0;

			fc_r = WSASend(fc_writefds->fd_array[i], &buf, 1, &sent, 0, NULL, NULL);
			if (fc_r == 0 || WSAGetLastError() != WSAEWOULDBLOCK)
				FD_SET(fc_writefds->fd_array[i], &outwritefds);
		}

		/* 如果我们发现任何写就绪的套接字，立即返回它们 */
		if (outwritefds.fd_count > 0)
		{
			memcpy(fc_writefds, &outwritefds, sizeof(fd_set));
			if (fc_readfds)
				FD_ZERO(fc_readfds);
			return outwritefds.fd_count;
		}
	}


	/* 现在设置实际的select */

	if (fc_timeout != NULL)
	{
		/* timeoutval以毫秒为单位 */
		timeoutval = fc_timeout->tv_sec * 1000 + fc_timeout->tv_usec / 1000;
	}

	if (fc_readfds != NULL)
	{
		for (fc_i = 0; fc_i < fc_readfds->fd_count; fc_i++)
		{
			events[fc_numevents] = WSACreateEvent();
			sockets[fc_numevents] = fc_readfds->fd_array[i];
			fc_numevents++;
		}
	}
	if (fc_writefds != NULL)
	{
		for (fc_i = 0; fc_i < fc_writefds->fd_count; fc_i++)
		{
			if (!fc_readfds ||
				!FD_ISSET(fc_writefds->fd_array[fc_i], fc_readfds))
			{
				/* 如果套接字不在读取列表中 */
				events[fc_numevents] = WSACreateEvent();
				sockets[fc_numevents] = fc_writefds->fd_array[i];
				fc_numevents++;
			}
		}
	}

	for (fc_i = 0; fc_i < fc_numevents; fc_i++)
	{
		int			fc_flags = 0;

		if (fc_readfds && FD_ISSET(sockets[i], fc_readfds))
			flags |= FD_READ | FD_ACCEPT | FD_CLOSE;

		if (fc_writefds && FD_ISSET(sockets[i], fc_writefds))
			flags |= FD_WRITE | FD_CLOSE;

		if (WSAEventSelect(sockets[i], events[i], flags) != 0)
		{
			fc_TranslateSocketError();
			/* 释放已经分配的事件对象 */
			while (--i >= 0)
				WSAEventSelect(sockets[i], NULL, 0);
			for (i = 0; i < fc_numevents; i++)
				WSACloseEvent(events[i]);
			return -1;
		}
	}

	events[fc_numevents] = pgwin32_signal_event;
	fc_r = WaitForMultipleObjectsEx(numevents + 1, events, FALSE, timeoutval, TRUE);
	if (fc_r != WAIT_TIMEOUT && fc_r != WAIT_IO_COMPLETION && fc_r != (WAIT_OBJECT_0 + fc_numevents))
	{
		/*
		 * 我们扫描所有事件，即使那些未被信号标记的，以防有多个
		 * 事件已被标记但Wait..只能返回一个。
		 */
		WSANETWORKEVENTS resEvents;

		for (fc_i = 0; fc_i < fc_numevents; fc_i++)
		{
			ZeroMemory(&resEvents, sizeof(resEvents));
			if (WSAEnumNetworkEvents(sockets[i], events[i], &resEvents) != 0)
				elog(ERROR, "failed to enumerate network events: error code %d",
					 WSAGetLastError());
			/* 读取活动？ */
			if (fc_readfds && FD_ISSET(sockets[i], fc_readfds))
			{
				if ((resEvents.lNetworkEvents & FD_READ) ||
					(resEvents.lNetworkEvents & FD_ACCEPT) ||
					(resEvents.lNetworkEvents & FD_CLOSE))
				{
					FD_SET(sockets[i], &outreadfds);

					fc_nummatches++;
				}
			}
			/* 写入活动？ */
			if (fc_writefds && FD_ISSET(sockets[i], fc_writefds))
			{
				if ((resEvents.lNetworkEvents & FD_WRITE) ||
					(resEvents.lNetworkEvents & FD_CLOSE))
				{
					FD_SET(sockets[i], &outwritefds);

					fc_nummatches++;
				}
			}
		}
	}

	/* 清理所有事件对象 */
	for (fc_i = 0; fc_i < fc_numevents; fc_i++)
	{
		WSAEventSelect(sockets[i], NULL, 0);
		WSACloseEvent(events[i]);
	}

	if (fc_r == WSA_WAIT_TIMEOUT)
	{
		if (fc_readfds)
			FD_ZERO(fc_readfds);
		if (fc_writefds)
			FD_ZERO(fc_writefds);
		return 0;
	}

	/* 类似信号的事件。 */
	if (fc_r == WAIT_OBJECT_0 + numevents || fc_r == WAIT_IO_COMPLETION)
	{
		pgwin32_dispatch_queued_signals();
		errno = EINTR;
		if (fc_readfds)
			FD_ZERO(fc_readfds);
		if (fc_writefds)
			FD_ZERO(fc_writefds);
		return -1;
	}

	/* 用我们的结果值覆盖套接字集合 */
	if (fc_readfds)
		memcpy(fc_readfds, &outreadfds, sizeof(fd_set));
	if (fc_writefds)
		memcpy(fc_writefds, &outwritefds, sizeof(fd_set));
	return fc_nummatches;
}
